package com.nce;

import java.io.File;
import java.io.IOException;
import java.util.Vector;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

public class WaveLoader extends Thread {
	 
	private String filename;
 
	private WavePlayerListener listener = null;
	private AudioFormat format = null;
	private Vector<byte[]>[] buffers = null;
	private boolean interrupted = false;
 
 
	public WaveLoader(String wavfile) {
		filename = wavfile;
	}
 
	
	public void setListener(WavePlayerListener l)
	{
		listener = l;
	}
 
	public void run() {

		File soundFile = new File(filename);
		if (!soundFile.exists()) {
			System.err.println("Wave file not found: " + filename);
			return;
		}
 
		AudioInputStream audioInputStream = null;
		try {
			audioInputStream = AudioSystem.getAudioInputStream(soundFile);
		} catch (UnsupportedAudioFileException e1) {
			e1.printStackTrace();
			return;
		} catch (IOException e1) {
			e1.printStackTrace();
			return;
		}
 
		format = audioInputStream.getFormat();
		int channels = format.getChannels();
		buffers = new Vector[channels];
		//auline = new SourceDataLine[channels];
		AudioFormat mono = new AudioFormat(format.getEncoding(), format.getSampleRate(), format.getSampleSizeInBits(), 1, format.getSampleSizeInBits()/8, format.getFrameRate(), format.isBigEndian());
		//DataLine.Info info = new DataLine.Info(SourceDataLine.class, mono);
		//feeders = new LineFeeder[channels];
		if (listener != null)
			listener.playerStarted();
		try {
			for (int i= 0; i< auline.length; i++)
			{
				auline[i] = (SourceDataLine) AudioSystem.getLine(info);
				auline[i].open(mono);
			}
		} catch (LineUnavailableException e) {
			e.printStackTrace();
			if (listener != null)
				listener.playerStopped();
			return;
		} catch (Exception e) {
			e.printStackTrace();
			if (listener != null)
				listener.playerStopped();
			return;
		}
		
		for (int i=0; i<auline.length; i++)
		{
			if (auline[i].isControlSupported(FloatControl.Type.PAN)) {
				FloatControl pan = (FloatControl) auline[i]
					.getControl(FloatControl.Type.PAN);
				if ((i%2) == 0) //pan right the even channels
				pan.setValue(1.0f);
				else 
					pan.setValue(-1.0f); //pan left the odd channels
				
			} 
			auline[i].start();
			feeders[i] = new LineFeeder(i);
		}
		
		int nBytesRead = 0;
		int size = (int)format.getSampleRate() * format.getFrameSize() * 20;
		byte[] abData = new byte[size];
		boolean firstBuffer = true;
		try {
			while (nBytesRead != -1) {
				if (interrupted)
				{
					break;
				}
				nBytesRead = audioInputStream.read(abData, 0, abData.length);
				if (nBytesRead >= 0)
				{
					byte[][] dataPerChannel = channelExtractor(format, abData, nBytesRead);
					for (int chan = 0; chan < auline.length; chan++)
					{
						feeders[chan].addData(dataPerChannel[chan]);
						if (firstBuffer)
						{
							feeders[chan].start();
						}
						//auline[chan].write(dataPerChannel[chan], 0, dataPerChannel[chan].length);
					}
					if (firstBuffer)
					{
						firstBuffer=false;
					}
				}
				
			}
		} catch (IOException e) {
			e.printStackTrace();
			if (listener != null)
				listener.playerStopped();
			return;
		} finally {
			for (SourceDataLine sdl : auline)
			{
				/*sdl.drain();
				sdl.close();*/
			}
		}
		for (LineFeeder lf : feeders)
		{
			try {
				lf.join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				//e.printStackTrace();
			}
		}
		listener.playerStopped();
	}
	
	@Override
	public void interrupt()
	{
		interrupted=true;
		listener.playerStopped();
		for (LineFeeder lf : feeders)
		{
			lf.interrupt();
		}
		super.interrupt();
	}	
	
	private byte[][] channelExtractor (AudioFormat f, byte[] buffer, int bRead)
	{
		int totalSamples = bRead / f.getFrameSize();
		int bytesPerSample = f.getSampleSizeInBits()/8;
		int size = totalSamples * bytesPerSample;
		byte[][] b = new byte[f.getChannels()][size];

		int index = 0;
		for (int sample = 0; sample<totalSamples; sample++)
		{
			for (int channel = 0; channel < f.getChannels(); channel++)
			{
				for (int byteInSample = 0; byteInSample < bytesPerSample; byteInSample++)
				{
					b[channel][sample * bytesPerSample + byteInSample] = buffer[index];
					index++;	
				}
			}
		}
		return b;
	}
	
	
	private class LineFeeder extends Thread
	{
		private boolean stopped = false;
		private int channel = 0;
		private Vector<byte[]> dataVector = null;
		private Object synch = null;
		//private boolean firstBuffer = true;
		
		public LineFeeder (int channel)
		{
			this.channel = channel;
			this.dataVector = new Vector<byte[]>();
			synch=new Object();
		}
		
		public void run()
		{
			byte [] data = null;
			while (!dataVector.isEmpty() && !stopped)
			{
				
				synchronized(synch)
				{
					 data = dataVector.remove(0);
				}
				auline[channel].write(data, 0, data.length);

			}
			auline[channel].drain();
			auline[channel].close();
		}
		
		public void addData(byte[] data)
		{
			synchronized(synch)
			{
				dataVector.add(data);
			}
		}
		
		@Override
		public void interrupt()
		{
			super.interrupt();
			stopped = true;
			auline[channel].stop();
		}
		
	}
}
